#include "ca.h"
#include "coordinate.h"

template <typename T>
CA<T>::CA(const T** init, const RuleSet<T>& r, const NBMask n): ruleset(r), nbmask(n), timeStep(0)
{
	reset (init);
}

template <typename T>
CA<T>::CA(RuleSet<T> r, const NBMask n): ruleset(r), nbmask(n), timeStep(0)
{
	reset();
}

template <typename T>
CA<T>::CA(): timeStep(0)
{
	reset();
}
template <typename T>
CA<T>::CA(const CA<T>& other): ruleset(other.ruleset), nbmask(other.nbmask), timeStep(other.timeStep)
{
	for (int row = 0; row < ROWNUM; row++)
		for (int col = 0; col < COLNUM; col++)
			try
		{
			cell[row][col].setState(other.cell[row][col].getState());
		}
		catch (...)
		{
			cerr << "Did you check the row& col size?\n";
		}
}

template <typename T>
RuleSet<T>& CA<T>::getRuleSet() const
{
	return (RuleSet<T>&) ruleset;
}

template <typename T>
NBMask& CA<T>::getNBMask() const
{
	return (NBMask&) nbmask;
}

template <typename T>
int CA<T>::getTime() const
{
	return timeStep;
}

template <typename T>
void CA<T>::reset()
{
	timeStep = 0;
	for (int row = 0; row < ROWNUM; row++)
		for (int col = 0; col < COLNUM; col++)
			try
		{
			cell[row][col].setState((T)0);
		}
		catch (...)
		{
			cerr << "Did you check the row& col size?\n";
		}
}

template <typename T>
void CA<T>::reset(const T** init)
{
	timeStep = 0;
	for (int row = 0; row < ROWNUM; row++)
		for (int col = 0; col < COLNUM; col++)
			try
		{
			cell[row][col].setState(init[row][col]);
		}
		catch (...)
		{
			cerr << "Did you check the row & col size?\n";
		}
}

template <typename T>
void CA<T>::loadState(const T** newState)
{
	assert (newState && "Null pointer passed to load the values from.");
	timeStep = 0;
	for (int row = 0; row < ROWNUM; row++)
		for (int col = 0; col < COLNUM; col++)
		{
			try
			{
				cell[row][col].setState(newState[row][col]);
			}
			catch (...)
			{
				cerr << "Did you check the row & col size?\n";
			}
		}
}

template<typename T>
T CA<T>::cellState(int row, int col) const
{
	T result;
	try
	{
		result = cell[row][col].getState();
	}
	catch (...)
	{
		cerr << "Trying to access an out of range cell";
	}
	return result;
}

template<typename T>
T CA<T>::cellState(int row, int col, int time) const
{
	assert (time >= timeStep && "Error: cannot tell the past state yet.");
	if (timeStep == time)
		return cellState(row,col);
	CA<T> tempCA(*this);
	for (int t = this->timeStep; t <= time; t++)
		tempCA.advance();
	return tempCA.cellState(row,col);
}

template<typename T>
T** CA<T>::state(void) const
{
	T** result = new T*[ROWNUM];
	for (int i = 0; i < ROWNUM; i++)
		result[i] = new T[COLNUM];

	for (int row = 0; row < ROWNUM; row++)
		for (int col = 0; col < COLNUM; col++)
			result[row][col] = cell[row][col].getState();
	

	return result;
}

template<typename T>
T** CA<T>::state(int time) const
{
	assert (time >= timeStep && "Error: cannot tell the past state yet.");
	if (timeStep == time)
		return state();
	CA<T> tempCA(*this);
	for (int t = this->timeStep; t <= time; t++)
		tempCA.advance();
	return tempCA.state();
}

template<typename T>
bool CA<T>::outOfBorders(const Coordinate& coo) const
{
	return (coo.row >= 0 && coo.col >= 0 && coo.row < ROWNUM && coo.col < COLNUM);
}

template<typename T>
vector<T>& CA<T>::nbValues(const Coordinate& coo) const
{
	assert (coo.row >= 0 && coo.col >= 0 && coo.row < ROWNUM && coo.col < COLNUM && "Error: out of CA limits.\n");
	vector<T> result;
	vector<Coordinate> nbCoos = nbmask.apply(coo);
	for (vector<Coordinate>::const_iterator i = nbCoos.begin(); i != nbCoos.end(); ++i)
	{
		if (outOfBorders(coo))
			result.push_back(OUTOFBORDER);
		else
			result.push_back(cell[coo.row][coo.col].getState());
	}
	return result;
}


template<typename T>
void CA<T>::advance()
{
	//T tempGrid [ROWNUM][COLNUM];
	T** tempGrid = new T*[ROWNUM];
	for (int row = 0 ; row < ROWNUM; ++row)
	{
		tempGrid[row] = new T[COLNUM];
		for (int col = 0; col < COLNUM; ++col)
		{
			try //try and catch is used because we might not have enough return values to report that a ruleset does not apply (e.g. binary CAs have only true and false).
			{
				tempGrid[row][col] = ruleset.outCome(nbValues(Coordinate(row,col)));
			}
			catch (myException e)
			{
				if (e == RULESETDOESNOTAPPLY) //the ruleset does not apply to this neighborhood, hold your last state.
					tempGrid[row][col] = cell[row][col].getState();
			}
		}
	}
		loadState((const T**)tempGrid);
		++timeStep;
}

template<typename T>
ostream& operator<< (ostream& os, const CA<T>& ca)
{
	for (int row = 0; row < ROWNUM; row++)
	{
		os << endl;
		for (int col = 0; col < COLNUM; col++)
			os << ca.cellState(row, col) << "\t";
	}
}



//adding to fix the linking problems:
template class CA<int>;